package sqlToGUI.charts;


import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.Properties;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JTextArea;

import sqlToGUI.HarpalykeGUI1;
import sqlToGUI.charts.helperUtilities.CoorPair;
import sqlToGUI.charts.helperUtilities.MouseClickDetector;
import sqlToGUI.charts.helperUtilities.TelemetryChartEvent;
import sqlToGUI.charts.helperUtilities.TelemetryChartListener;
import sqlToGUI.charts.helperUtilities.TelemetryDataEntry;


public class DistanceMap implements GUIDisplay {

	
	/**The x location of the car on the map */
	public static int carX = 50;
	/**The y location of the car on the map */
	public static int carY = 50;
	
	/**The list receiving the new data from the outside -
	 * This class will be responsible for listening to this
	 * list and updating from it whenever new information
	 * is available -
	 * The array is used in case chart uses reference to more
	 * than one data set and is updated from more than one
	 * list */
	private TelemetryDataEntry [] dataEntries;
	
	/**Indicate whether mouse click listener is on or off:
	 * useful when detecting clicks on a map for knowing
	 * where to create the key location */
	private boolean mouseClickOn = true;
	
	/**File storing information about which map of what size to load */
	private Properties australianMap;
	
	/**The ration between the size of the map and the GUI interface */
	private double mapWidthToGUI;
	
	/**The map showing the distance travelled so far and the
	 * corresponding location */
	private Container distanceMap;
	
	/**Buffer storing the coordinates of the key locations on the map */
	private CoorPair[] buffer;
	
	/**The width of the image */
	private int mapWidth;
	
	/**The total distance the route on the map represents */
	private int totalDistanceOnMap;
	
	/**The current distance travelled on the map */
	private int currentDistanceTravelled;
	
	/**The distance left to travel on the map */
	private int distanceLeftToTravel;
	
	/**For each checkpoint provided to be display on the map,
	 * this array will store the current distance travelled up
	 * to that check point */
	private int[] distanceForEachCoordinate;
	
	/**The log panel data will monitor the incoming data */
	private LogPanel log;
	/**Variable indicating whether or not a log has been
	 * registered */
	private boolean logReg = false;
	
	/**Class constructor */
	public DistanceMap(){
		
		/**Initialize the properties for holding information
		 * about the map display */
		try {
			australianMap = new Properties();
			australianMap.load(new FileInputStream
					("properties/australian_map.properties"));
		} catch (FileNotFoundException e) {
			System.out.println("DistanceMap: Failed to read property file");
			System.exit(1);
		} catch (IOException e) {
			System.out.println("DistanceMap: Failed to read property file");
			System.exit(1);
		}

		/**Get the total distance represented on the map */
		totalDistanceOnMap = Integer.parseInt(australianMap.
				getProperty("totalDistanceToTravel"));
		
		this.currentDistanceTravelled = 0;
		this.distanceLeftToTravel = totalDistanceOnMap;
		
		/**Get the ratio between the map width and the GUI -
		 * forgot what this is for specifically */
		mapWidthToGUI = Double.parseDouble(australianMap.
				getProperty("mapWidthToGUI"));
		
		/**Get the required image width*/
		mapWidth = (int)
		((Math.round((HarpalykeGUI1.mainWindowx*HarpalykeGUI1.resizer
				*(mapWidthToGUI))/100))*100);
		
		
		int numOfLocations = Integer.parseInt(australianMap.
				getProperty("numberOfMarkedLocations"));
		
		
		/**Initialize the array for storing the current distance travelled
		 * up to each checkpoint on the map */
		distanceForEachCoordinate = new int[numOfLocations];
		for(int i = 0; i < numOfLocations; i++){
			distanceForEachCoordinate[i] = Integer.parseInt(australianMap.
					getProperty("totalDistanceAtCoord" + (i+1)));
		}
		
		/**Initialize the coordinate locations for the map */
		buffer = new CoorPair[numOfLocations];
		for(int i = 1; i <= numOfLocations; i++){
			buffer[i-1] = new CoorPair(
					Integer.parseInt(australianMap.
				getProperty(mapWidth + "coord" + i + "x")),
					Integer.parseInt(australianMap.
				getProperty(mapWidth + "coord" + i + "y")));
		}
		
		/**Create the distance map itself */
		createDistanceMap();
	}
	
	/**Method for creating the map and the distance on it */
	public void createDistanceMap() {

		/**Load the image with a specific size corresponding to the
		 * image width*/
		String imageToLoad = australianMap.getProperty("width" + mapWidth);
		final ImageIcon mapIcon = new ImageIcon("map/" + imageToLoad);		
		System.out.println("Distance Map: map loaded: " + imageToLoad);

		/**Load the image for marking positions on the map */
		final ImageIcon marker = new ImageIcon("map/mark.jpg");
				
		/**Load the image marking the position of the car */
		final ImageIcon carIcon = new ImageIcon("map/car.jpg");
		
		/**Using text area to hold the map since it is much easier to control
		 * where the coordinates are placed */
		JTextArea textArea = new JTextArea() {
			
			private static final long serialVersionUID = 34091594553818300L;
			
			Image image1 = mapIcon.getImage();
			Image image2 = marker.getImage();
			Image image3 = carIcon.getImage();
			
			/**No idea how this method is calledd exactly...
			 * but then again, it works well :D */
			{setOpaque(false);}  
			public void paintComponent (Graphics g) {
				
				/**Some weird calculations that will adjust the image given
				 * its width - this was tough with the lazy effort so
				 * the actual thought process for this is quite random */
				int locWidthRatio = mapWidth/500;
				
				/**Set the colour for the graphics - used for lines */
				g.setColor(Color.ORANGE);
				
				/**Draw the map itself first */
				g.drawImage(image1, (int)(-80*locWidthRatio), 
						(int)(-15*locWidthRatio), this);
				
				/**Then draw the lines - create double lines
				 * for extra thickness... its also looks better...
				 * like miniature highways */
				for(int i = 0; i < buffer.length-1; i++){
					g.drawLine((int)(buffer[i].getX()), 
						(int)(buffer[i].getY()),
							   (int)(buffer[i+1].getX()), 
						(int)(buffer[i+1].getY()));
					g.drawLine((int)(buffer[i].getX()+1), 
							(int)(buffer[i].getY()+1),
								   (int)(buffer[i+1].getX()+1), 
							(int)(buffer[i+1].getY())+1);
				}
				
				/**Draw the points - subtract and add one from the
				 * locations since the images can't center themselves on the
				 * provided coordinates */
				for(int i = 0; i < buffer.length; i++){g.drawImage(image2,
						(int)(buffer[i].getX()*locWidthRatio)-2,
						(int)(buffer[i].getY()*locWidthRatio)-2, this);
				}
				
				/**Draw the starting location of the car */
				g.drawImage(image3, carX, carY, this);
				
				super.paintComponent(g);
			}
		};
		
		/**While trying to figure out where to place additional points on
		 * the map, I figured a listener showing the location of map clicks
		 * would be useful - this checks if aid is disabled or not */
		if(mouseClickOn) {
			textArea.addMouseListener(new MouseClickDetector());
		}
		
		/**Set the text area to display quantitative distance information*/
		Font font1 = new Font("monospaced", Font.BOLD, 16);
		textArea.setFont(font1);
		textArea.setText("Current Distance Travelled:   " +
				totalDistanceOnMap +  "\n" +
				"Distance Left To Travel:      " + 0);
		textArea.setForeground(Color.BLUE);
		
		/**Container needs to be taken from a frame, since it doesn't show
		 * up otherwise in the GUI */
		JFrame temp1 = new JFrame();
		distanceMap = temp1.getContentPane();
		
		/**Finally add the everything to the main display object */
		distanceMap.add(textArea);
		distanceMap.repaint();
	}
	
	public void addToBuffer(int index, double value) {
	
	}

	/**Register the given log so that it can monitor incoming data */
	public GUIDisplay registerLog(LogPanel log){
		this.log = log;
		logReg = true;
		return this;
	}
	
	/**Given the current location as input, find the closest
	 * checkpoint on the map - in terms of rounding down to the last
	 * coordinate */
	private int getClosestCheckPointLocation(int givenLoc){
		
		/**The closest distance between the checkpoint and the current
		 * location found so far */
		int closestDistance = totalDistanceOnMap;
		/**The index of that checkpoint */
		int closestIndex = 0;
		
		int currentDistance;
		
		/**Iterate through every checkpoint and find the one with the closest
		 * distance (in respect to the left on the numerical line) from the
		 * given location */
		for(int i = 0; i < distanceForEachCoordinate.length; i++){
			currentDistance = givenLoc - distanceForEachCoordinate[i];
			if(currentDistance >= 0 && currentDistance < closestDistance){
				closestIndex = i;
				closestDistance = currentDistance;
			}
		}
		
		return closestIndex;
	}
	
	/**Return the buffer for updating the distance on the map */
	public TelemetryDataEntry[] getBuffers() {
		dataEntries = new TelemetryDataEntry [1];
		for(int i = 0; i < dataEntries.length; i++) {
			dataEntries[i] = new TelemetryDataEntry(1);
		}
			
		/**Create the event(update chart) when this buffer receives
		 * new data */
		dataEntries[0].addTelemetryChartEventListener(
				new TelemetryChartListener() {
			public void myEventOccurred(TelemetryChartEvent evt) {
				
				/**Take the given distance travelled from the input
				 * and figure out where its supposed to go on the map
				 * based on the location of the created checkpoints
				 * and the total distance assigned to each checkpoint */
				int indexOfClosestCheckPoint = 
					getClosestCheckPointLocation(
							(int)(dataEntries[0].getUpdate()[0]));
				
				
	
				int distanceBetweenCheckPoint = (int)dataEntries[0].
								getUpdate()[0] -
						distanceForEachCoordinate[indexOfClosestCheckPoint];
				
				/**If the current distance is occupied by a given checkpoint,
				 * mark the car on the map directly on the checkpoint */
				/**The number ten is used instead of zero since subconciously
				 * I know that errors will occur otherwise, not sure why
				 * however... ten is a safe number */
				if(distanceBetweenCheckPoint <= 10 &&
						indexOfClosestCheckPoint+1 < buffer.length){
					
				    DistanceMap.carX = buffer[indexOfClosestCheckPoint].getX()-2;
					DistanceMap.carY = buffer[indexOfClosestCheckPoint].getY()-2;
					
					distanceMap.repaint();

					currentDistanceTravelled = 
						(int)(dataEntries[0].getUpdate()[0]);
					
					distanceLeftToTravel = 
						(int)(totalDistanceOnMap -
								dataEntries[0].getUpdate()[0]);
				}
				
				/**Otherwise located the car accurately between two
				 * checkpoints, using the distance between checkpoint i
				 * and checkpoint i+1, and the distance of the current
				 * location from checkpoint i*/
				if(distanceBetweenCheckPoint > 10 &&
						indexOfClosestCheckPoint+1 < buffer.length){
					
					/**Distance between checkpoint i and i + 1 */
					int distBetweenChecki1i2 =
						distanceForEachCoordinate[indexOfClosestCheckPoint] -
						distanceForEachCoordinate[indexOfClosestCheckPoint+1];
					
					/**Ration of the distance between checkpoint i and i + 1
					 * to the distance from checkpoint i */
					double ratioToCheckpoints = 
						((distanceBetweenCheckPoint+0.0)/
								(distBetweenChecki1i2+0.0));
					
					/**Coordinates of the car given its total distance
					 * travelled and the calculations provided above */
					int carX = (int)(
							(buffer[indexOfClosestCheckPoint+1].getX() - 
							buffer[indexOfClosestCheckPoint].getX() + 0.0)*
							ratioToCheckpoints);
					int carY = (int)(
							(buffer[indexOfClosestCheckPoint+1].getY() - 
							buffer[indexOfClosestCheckPoint].getY()+ 0.0)*
							ratioToCheckpoints);

					/**Draw the location of the car given the calculated
					 * coordinates */
					DistanceMap.carX = buffer[indexOfClosestCheckPoint].getX()
										- carX - 2;
					DistanceMap.carY =  buffer[indexOfClosestCheckPoint].getY()
										- carY - 2;			
					distanceMap.repaint();

					/**Update info on the current distance travelled and
					 * the distance from the final destination */
					currentDistanceTravelled = 
						(int)(dataEntries[0].getUpdate()[0]);

					distanceLeftToTravel = 
						(int)(totalDistanceOnMap -
								dataEntries[0].getUpdate()[0]);
					
					((JTextArea) distanceMap.getComponent(0)).
						setText("Current Distance Travelled:   " +
								currentDistanceTravelled +  "\n" +
								"Distance Left To Travel:      " +
								distanceLeftToTravel);
					
				}
				
				/**Case when car is at the finish */
				if(indexOfClosestCheckPoint+1 == buffer.length){
					
					/**Draw the location of the car given the calculated
					 * coordinates */
					DistanceMap.carX = buffer[indexOfClosestCheckPoint].
						getX() - 2;
					DistanceMap.carY =  buffer[indexOfClosestCheckPoint].
						getY() - 2;			
					distanceMap.repaint();

					/**Update info on the current distance travelled and
					 * the distance from the final destination */
					currentDistanceTravelled = totalDistanceOnMap;
					distanceLeftToTravel = 0;
					
					((JTextArea) distanceMap.getComponent(0)).
						setText("Current Distance Travelled:   " +
								currentDistanceTravelled +  "\n" +
								"Distance Left To Travel:      " +
								distanceLeftToTravel);
				}
			}
		});
		
		/**TODO: send data to the log*/
		
		return dataEntries;
	}

	/**Return the distance map */
	public Container getChart() {
		return distanceMap;
	}

}